import rbush from "rbush"; //https://www.5axxw.com/wiki/content/7wjc4t

export var CanvasLabel = (L.CanvasLabel = L.Canvas.extend({
    options: {
        defaultLabelStyle: {
            offsetX: 0, //横坐标偏移(像素)
            offsetY: 0, //纵坐标偏移(像素)
            scale: 1, //放大比例
            rotation: 0, //旋转角度（弧度），可能会导致碰撞检测不准确
            text: null, //标注文本内容
            minZoom: null, //最小显示级别
            maxZoom: null, //最大显示级别
            collisionFlg: true, //碰撞检测
            collisionFlg2: false,
            center: null, //标注位置，默认为null,会自动计算几何中心
            zIndex: 0, //排序
            defaultHeight: 20, //文本高度,无法自动计算,所以直接传参手动调整

            //文本样式,具体值请参考[canvas](https://www.runoob.com/tags/ref-canvas.html)
            font: "10px sans-serif",
            fillStyle: "rgba(0,0,0,1)",
            lineCap: "round",
            lineDash: [],
            lineDashOffset: 0,
            lineJoin: "round",
            strokeStyle: "rgba(0,0,0,1)",
            textAlign: "center",
            textBaseline: "middle",
            lineWidth: 1,
        },
    },
    getEvents: function () {
        var events = {
            viewreset: this._reset,
            zoom: this._onZoom,
            moveend: this._update,
            zoomend: this._onZoomEnd,
            click: this._executeListeners,
            mousemove: this._executeListeners,
            mousedown: this._executeListeners,
            mouseup: this._executeListeners,
        };
        if (this._zoomAnimated) {
            events.zoomanim = this._onAnimZoom;
        }
        return events;
    },
    initialize: function (options) {
        let that = this
        this._intervalId = setInterval(function() {
            that._update()
            that._draw()
        },1000)
        this._onClickListeners = [];
        this._onHoverListeners = [];
        this._onMouseDownListeners = [];
        this._onMouseUpListeners = [];

        options.defaultLabelStyle = options.defaultLabelStyle || {};
        options.defaultLabelStyle = L.extend(
            {},
            this.options.defaultLabelStyle,
            options.defaultLabelStyle
        );
        L.Canvas.prototype.initialize.call(this, options);
    },
    onDestroy: function() {  
        // 清除定时器  
        if (this._intervalId) {  
            clearInterval(this._intervalId);  
            this._intervalId = null;  
        }  
  
        // 如果需要，还可以在这里添加其他销毁时的清理代码  
    },  
    /**
     * 继承L.Canvas方法
     * 在刷新时保存画布当前地理位置
     */
    _update: function () {
        this._latlngBounds = this._map.getBounds().pad(this.options.padding);
        L.Canvas.prototype._update.call(this);
    },

    _draw: function () {
        if (!this._textBounds) {
            this._textBounds = new rbush();
        } else {
            this._textBounds.clear();
        }
        let drawLayers = [];

        var layer,
            bounds = this._redrawBounds;
        this._ctx.save();
        if (bounds) {
            var size = bounds.getSize();
            this._ctx.beginPath();
            this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
            this._ctx.clip();
        }

        this._drawing = true;

        for (var order = this._drawFirst; order; order = order.next) {
            layer = order.layer;
            if (
                !bounds ||
                (layer._pxBounds && layer._pxBounds.intersects(bounds))
            ) {
                drawLayers.push(layer);
            }
        }
        for (let i = 0; i < drawLayers.length; i++) {
            drawLayers[i]._updatePath();
        }

        //筛选需要绘制标注的图层
        let labelLayers = drawLayers.filter(function (layer) {
            {
                return layer.options.labelStyle && layer.options.labelStyle.text;
            }
        });
        //筛选不做碰撞检测的标签图层并安装zIndex排序
        let notCollisionLayers = labelLayers.filter((layer) => {
            var collisionFlg =
                layer.options.labelStyle.collisionFlg != undefined
                    ? layer.options.labelStyle.collisionFlg
                    : this.options.defaultLabelStyle.collisionFlg2;
            return collisionFlg != true;
        });
        //不需要做碰撞检测的标注升序排序,zIndex值大的后绘制,会覆盖在先绘制的标注上面
        notCollisionLayers
            .sort((layer1, layer2) => {
                let zIndex1 = layer1.options.labelStyle.zIndex
                    ? layer1.options.labelStyle.zIndex
                    : this.options.defaultLabelStyle.zIndex;
                let zIndex2 = layer2.options.labelStyle.zIndex
                    ? layer2.options.labelStyle.zIndex
                    : this.options.defaultLabelStyle.zIndex;
                return zIndex1 - zIndex2;
            })
            .forEach((layer) => {
                this._updateText(this._ctx, layer);
            });

        //筛选需要碰撞检测的标签图层并安装zIndex排序
        let collisionLayers = labelLayers.filter((layer) => {
            var collisionFlg =
                layer.options.labelStyle.collisionFlg != undefined
                    ? layer.options.labelStyle.collisionFlg
                    : this.options.defaultLabelStyle.collisionFlg;
            return collisionFlg == true;
        });

        //需要做碰撞检测的标注降序排序,zIndex值大的优先绘制
        collisionLayers
            .sort((layer1, layer2) => {
                let zIndex1 = layer1.options.labelStyle.zIndex
                    ? layer1.options.labelStyle.zIndex
                    : this.options.defaultLabelStyle.zIndex;
                let zIndex2 = layer2.options.labelStyle.zIndex
                    ? layer2.options.labelStyle.zIndex
                    : this.options.defaultLabelStyle.zIndex;
                return -zIndex1 + zIndex2;
            })
            .forEach((layer) => {
                this._updateText(this._ctx, layer);
            });

        this._drawing = false;

        this._ctx.restore(); // Restore state before clipping.
    },

    /**
     * 更新文本标注
     */
    _updateText: function (ctx, layer) {
        let that = this
        //没有标签样式或没有标签文本的直接退出
        if (!layer.options.labelStyle || !layer.options.labelStyle.text) {
            return;
        }
        //计算图形中心点
        var latlng = L.latLng(layer.options.labelStyle.center);
        if (latlng) {
        } else if (layer.getLatLng) {
            latlng = layer.getLatLng();
        } else {
            //线，面没有环的直接退出
            if (layer._parts.length == 0 || layer._parts[0].length == 0) {
                return;
            }
            latlng = layer.getCenter();
        }

        //图形中心点没有在可视区域内的直接退出
        if (!this._latlngBounds.contains(latlng)) {
            return;
        }

        let layerLabelStyle = layer.options.labelStyle;
        let defaultLabelStyle = L.extend({}, this.options.defaultLabelStyle);
        //图层标注样式是个函数
        if (typeof layerLabelStyle == "function") {
            layerLabelStyle = layerLabelStyle(layer);
        }

        //最终标注样式
        let labelStyle = L.extend(defaultLabelStyle, layer.options.labelStyle);

        //地图缩放级别小于标注最小显示级别直接退出
        if (labelStyle.minZoom) {
            if (this._map.getZoom() < labelStyle.minZoom) {
                return;
            }
        }

        //地图缩放级别大于标注最大显示级别直接退出
        if (labelStyle.maxZoom) {
            if (this._map.getZoom() > labelStyle.maxZoom) {
                return;
            }
        }
        //保持画布原本样式
        ctx.save();

        //设置画布样式
        ctx.font = labelStyle.font;
        ctx.fillStyle = labelStyle.fillStyle;
        ctx.lineCap = labelStyle.lineCap;
        ctx.lineDash = labelStyle.lineDash;
        ctx.lineDashOffset = labelStyle.lineDashOffset;
        ctx.lineJoin = labelStyle.lineJoin;
        ctx.strokeStyle = labelStyle.strokeStyle;
        ctx.textAlign = labelStyle.textAlign;
        ctx.textBaseline = labelStyle.textBaseline;
        ctx.lineWidth = labelStyle.lineWidth;

        // 标注偏移
        var offsetX = labelStyle.offsetX;
        var offsetY = labelStyle.offsetY;
        //相对于原点的相应像素坐标
        var p = this._map.latLngToLayerPoint(latlng);

        //计算标注像素坐标
        var x = p.x + offsetX;
        var y = p.y + offsetY;

        //设置标注坐标为中心点(这样可以直接进行缩放与旋转而不用考虑其他因素，实现后通过还原画布不会影响其他效果)
        ctx.translate(x, y);

        //缩放比例不为1
        if (labelStyle.scale != 1) {
            ctx.scale(labelStyle.scale, labelStyle.scale);
        }

        //旋转角度不为0
        if (labelStyle.rotation != 0) {
            ctx.rotate(labelStyle.rotation);
        }

        // 碰撞检测
        var textWidth =ctx.measureText(labelStyle.text).width * labelStyle.scale;
        var textHeight = labelStyle.defaultHeight * labelStyle.scale;
        let minX, minY, maxX, maxY;

        //https://www.runoob.com/tags/canvas-textalign.html
        if (labelStyle.textAlign == "center") {
            minX = x - textWidth / 2;
            maxX = x + textWidth / 2;
        } else if (
            labelStyle.textAlign == "start" ||
            labelStyle.textAlign == "left"
        ) {
            minX = x;
            maxX = x + textWidth;
        } else if (
            labelStyle.textAlign == "end" ||
            labelStyle.textAlign == "right"
        ) {
            minX = x - textWidth;
            maxX = x;
        } else {
            console.error(
                "textAlign的值必须是start，end，left，center，right中的一个！"
            );
        }

        //https://www.runoob.com/tags/canvas-textBaseline.html
        if (labelStyle.textBaseline == "middle") {
            minY = y - textHeight / 2;
            maxY = y + textHeight / 2;
        } else if (
            labelStyle.textBaseline == "top" ||
            labelStyle.textBaseline == "hanging"
        ) {
            minY = y;
            maxY = y + textHeight;
        } else if (
            labelStyle.textBaseline == "bottom" ||
            labelStyle.textBaseline == "alphabetic"
        ) {
            minY = y - textHeight;
            maxY = y;
        } else {
            console.error(
                "textBaseline的值必须是middle，top，hanging，bottom，alphabetic中的一个！"
            );
        }

        let textBounds = { minX, minY, maxX, maxY, layer };
        if (
            !(
                this._zoom < 19 && 
                labelStyle.collisionFlg == true &&
                this._textBounds.collides(textBounds)
            )
        ) {
            //绘制标注

            ctx.rotate(-this._map.getBearing()/57.3)
            ctx.strokeText(labelStyle.text, 0, 0);
            ctx.fillText(labelStyle.text, 0, 0);
            this._textBounds.insert(textBounds);
        }

        //还原画布样式
        ctx.restore();
    },
    /**
     * 执行侦听器
     */
    _executeListeners: function (event) {
        if (!this._textBounds) return;
        var me = this;
        var ret = this.getTextByEvent(event);
        if (ret && ret.length > 0) {
            me._map._container.style.cursor = "pointer";
            if (event.type === "click") {
                me._onClickListeners.forEach(function (listener) {
                    listener(event, ret);
                });
            }
            if (event.type === "mousemove") {
                me._onHoverListeners.forEach(function (listener) {
                    listener(event, ret);
                });
            }
            if (event.type === "mousedown") {
                me._onMouseDownListeners.forEach(function (listener) {
                    listener(event, ret);
                });
            }

            if (event.type === "mouseup") {
                me._onMouseUpListeners.forEach(function (listener) {
                    listener(event, ret);
                });
            }
        } else {
            me._map._container.style.cursor = "";
        }
    },
    /**
     * 添加click侦听器
     */
    addOnClickListener: function (listener) {
        this._onClickListeners.push(listener);
    },

    /**
     * 添加hover侦听器
     */
    addOnHoverListener: function (listener) {
        this._onHoverListeners.push(listener);
    },

    /**
     * 添加mousedown侦听器
     */
    addOnMouseDownListener: function (listener) {
        this._onMouseDownListeners.push(listener);
    },

    /**
     * 添加mouseup侦听器
     */
    addOnMouseUpListener: function (listener) {
        this._onMouseUpListeners.push(listener);
    },
    getTextByEvent(event){
        var x = event.layerPoint.x;
        var y = event.layerPoint.y;

        var ret = this._textBounds.search({
            minX: x,
            minY: y,
            maxX: x,
            maxY: y,
        });
        return ret;
    }
}));

export var canvasLabel = (L.canvasLabel = function (options) {
    return new L.CanvasLabel(options);
});
